package ferp.core.ai.nn;

//import com.google.gson2.Gson;
//import com.google.gson2.GsonBuilder;

import ferp.core.ai.nn.activation.Activation;

import java.io.Serializable;

/**
 * User: igorgok
 * Date: 12/17/12 6:30 PM
 */
public class NN implements Serializable
{
  // only weights matter for storing the net
  public double[][][] weights;
  public Activation[] activations;
  public transient double[][] outputs;

  // create matrix of given size
  public static double[][] matrix(int units, int inputs)
  {
    double[][] m = new double[units][];

    for (int i = 0; i < units; ++i)
      m[i] = new double[inputs];

    return m;
  }

  public NN(int[] sizes, Activation[] activations)
  {
    int layers = sizes.length - 1;

    // create weights according to the number of layers
    weights = new double[layers][][];

    // sizes[0] - input layer size (this layer has no weights)
    for (int i = 0; i < layers; ++i)
      weights[i] = matrix(sizes[i + 1], sizes[i] + 1);

    // set activation functions for each layer (except the input layer)
    if (activations.length < getActiveLayersCount())
      throw new RuntimeException("AF array length must be no less than " + getActiveLayersCount());

    this.activations = activations;
  }

  public NN(int[] sizes, Activation template)
  {
    this(sizes, getActivationsFromTemplate(template, sizes.length - 1));
  }

  private static Activation[] getActivationsFromTemplate(Activation template, int size)
  {
    Activation[] a = new Activation[size];

    for (int i = 0; i < size; ++i)
      a[i] = template;

    return a;
  }

  public int getLayerSize(int layer) {return weights[layer].length;}
  public int getLayerArity(int layer) {return weights[layer][0].length - 1;}
  public int getInputSize() {return getLayerArity(0);}
  public int getOutputSize() {return weights[getHiddenLayersCount()].length;}
  public int getHiddenLayersCount() {return weights.length - 1;}
  public int getActiveLayersCount() {return weights.length;}

  public double[] process(double[] inputs)
  {
    if (DEBUG)
      System.out.println("nn.process");

    // create outputs of each layer if required
    if (outputs == null)
    {
      outputs = new double[getActiveLayersCount()][];

      // outputs (does not include bias)
      for (int i = 0; i < outputs.length; ++i)
        outputs[i] = new double[getLayerSize(i)];
    }

    for (int l = 0, layers = getHiddenLayersCount() + 1; l < layers; ++l)
    {
      if (DEBUG)
        System.out.println("  l/" + l);

      for (int u = 0, units = getLayerSize(l); u < units; ++u)
      {
        double[] w = weights[l][u];
        // bias weight is at position 0
        double sum = w[0];

        for (int k = 1; k < w.length; ++k)
          sum += w[k] * inputs[k - 1];

        outputs[l][u] = activations[l].activate(sum);

        if (DEBUG)
          System.out.println("    u/" + u + " output: " + outputs[l][u]);
      }

      // inputs for the next layer
      inputs = outputs[l];
    }

    return outputs[outputs.length - 1];
  }

  // serialize
  //public String serialize() {return gson.toJson(this);}

  // customized GSON object
  //private static final Gson gson = new GsonBuilder().registerTypeAdapter(Activation.class, new Activation.Adapter()).create();
  private static final boolean DEBUG = false;

  //private static final Random random = new Random();


/*
  public static void normalize(double[] values)
  {
    for (int i = 0; i < values.length; ++i)
      values[i] = values[i] < 0.5 ? 0 : 1;
  }

  public static void main(String[] args)
  {
    double xor[][] = {
        {0, 0, 0},
        {0, 1, 1},
        {1, 0, 1},
        {1, 1, 0},
    };

    BPNN bpnn = new BPNN(new int[]{2, 2, 1}, new Activation[]{Sigmoid.instance, Sigmoid.instance});
    Backprop bp2 = new Backprop(bpnn);

    //double[][] normalized = normalize(sizes);
    int i = 0;

    bp2.train(xor, 0.1, 0.9, 0.1);

    for (double[] x : xor)
    {
      double[] r = bpnn.process(x);

      System.out.println(xor[i][0] + "," + xor[i][1] + " -> " + xor[i][2] + " === " + r[0]);

      ++i;
    }

    // snapshot length
    String json = gson.toJson(bpnn);

    System.out.println("save length: " + json.length());
    System.out.println(json);

    BPNN restored = gson.fromJson(json, BPNN.class);

    System.out.println("done");
  }

  private static double normalize(double v, double max)
  {
    return v / max;
  }

  private static double restore(double v, double max)
  {
    return v * max;
  }

  private static double[][] normalize(double[][] a)
  {
    double[][] b = new double[a.length][a[0].length];

    for (int i = 0; i < a.length; ++i)
    {
      b[i][0] = normalize(a[i][0], 2560);
      b[i][1] = normalize(a[i][1], 2560);
      b[i][2] = normalize(a[i][2], 200);
      b[i][3] = normalize(a[i][3], 200);
    }

    return b;
  }
*/
}
